package com.ihr360.job.core.listener;

import com.ihr360.job.core.ChunkListener;
import com.ihr360.job.core.ItemProcessListener;
import com.ihr360.job.core.ItemReadListener;
import com.ihr360.job.core.ItemWriteListener;
import com.ihr360.job.core.SkipListener;
import com.ihr360.job.core.StepExecutionListener;
import com.ihr360.job.core.StepListener;
import com.ihr360.job.core.annotation.AfterChunk;
import com.ihr360.job.core.annotation.AfterChunkError;
import com.ihr360.job.core.annotation.AfterProcess;
import com.ihr360.job.core.annotation.AfterRead;
import com.ihr360.job.core.annotation.AfterStep;
import com.ihr360.job.core.annotation.AfterWrite;
import com.ihr360.job.core.annotation.BeforeChunk;
import com.ihr360.job.core.annotation.BeforeProcess;
import com.ihr360.job.core.annotation.BeforeRead;
import com.ihr360.job.core.annotation.BeforeStep;
import com.ihr360.job.core.annotation.BeforeWrite;
import com.ihr360.job.core.annotation.OnProcessError;
import com.ihr360.job.core.annotation.OnReadError;
import com.ihr360.job.core.annotation.OnSkipInProcess;
import com.ihr360.job.core.annotation.OnSkipInRead;
import com.ihr360.job.core.annotation.OnSkipInWrite;
import com.ihr360.job.core.annotation.OnWriteError;
import com.ihr360.job.core.entity.StepExecution;
import com.ihr360.job.core.scope.context.ChunkContext;
import com.ihr360.job.core.scope.context.SimpleStepContext;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Enumeration for {@link StepListener} meta data, which ties together the names
 * of methods, their interfaces, annotation, and expected arguments.
 *
 * @author Lucas Ward
 * @see StepListenerFactoryBean
 * @since 2.0
 */
public enum StepListenerMetaData implements ListenerMetaData {
    BEFORE_STEP("beforeStep", "before-step-method", BeforeStep.class, StepExecutionListener.class, StepExecution.class),
    AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class),
    BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class, ChunkContext.class),
    AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class, ChunkContext.class),
    AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class, ChunkContext.class),
    BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class,SimpleStepContext.class),
    AFTER_READ("afterRead", "after-read-method", AfterRead.class, ItemReadListener.class, Object.class),
    ON_READ_ERROR("onReadError", "on-read-error-method", OnReadError.class, ItemReadListener.class, Exception.class),
    BEFORE_PROCESS("beforeProcess", "before-process-method", BeforeProcess.class, ItemProcessListener.class, Object.class, SimpleStepContext.class),
    AFTER_PROCESS("afterProcess", "after-process-method", AfterProcess.class, ItemProcessListener.class, Object.class, Object.class),
    ON_PROCESS_ERROR("onProcessError", "on-process-error-method", OnProcessError.class, ItemProcessListener.class, Object.class,SimpleStepContext.class, Exception.class),
    BEFORE_WRITE("beforeWrite", "before-write-method", BeforeWrite.class, ItemWriteListener.class, List.class),
    AFTER_WRITE("afterWrite", "after-write-method", AfterWrite.class, ItemWriteListener.class, List.class),
    ON_WRITE_ERROR("onWriteError", "on-write-error-method", OnWriteError.class, ItemWriteListener.class, Exception.class, List.class),
    ON_SKIP_IN_READ("onSkipInRead", "on-skip-in-read-method", OnSkipInRead.class, SkipListener.class, Throwable.class),
    ON_SKIP_IN_PROCESS("onSkipInProcess", "on-skip-in-process-method", OnSkipInProcess.class, SkipListener.class, Object.class, Throwable.class),
    ON_SKIP_IN_WRITE("onSkipInWrite", "on-skip-in-write-method", OnSkipInWrite.class, SkipListener.class, Object.class, Throwable.class);

    private final String methodName;
    private final String propertyName;
    private final Class<? extends Annotation> annotation;
    private final Class<? extends StepListener> listenerInterface;
    private final Class<?>[] paramTypes;
    private static final Map<String, StepListenerMetaData> propertyMap;

    StepListenerMetaData(String methodName, String propertyName, Class<? extends Annotation> annotation, Class<? extends StepListener> listenerInterface, Class<?>... paramTypes) {
        this.methodName = methodName;
        this.propertyName = propertyName;
        this.annotation = annotation;
        this.listenerInterface = listenerInterface;
        this.paramTypes = paramTypes;
    }

    static {
        propertyMap = new HashMap<String, StepListenerMetaData>();
        for (StepListenerMetaData metaData : values()) {
            propertyMap.put(metaData.getPropertyName(), metaData);
        }
    }

    @Override
    public String getMethodName() {
        return methodName;
    }

    @Override
    public Class<? extends Annotation> getAnnotation() {
        return annotation;
    }

    @Override
    public Class<?> getListenerInterface() {
        return listenerInterface;
    }

    @Override
    public Class<?>[] getParamTypes() {
        return paramTypes;
    }

    @Override
    public String getPropertyName() {
        return propertyName;
    }

    /**
     * Return the relevant meta data for the provided property name.
     *
     * @param propertyName
     * @return meta data with supplied property name, null if none exists.
     */
    public static StepListenerMetaData fromPropertyName(String propertyName) {
        return propertyMap.get(propertyName);
    }

    public static ListenerMetaData[] itemListenerMetaData() {
        return new ListenerMetaData[]{BEFORE_WRITE, AFTER_WRITE, ON_WRITE_ERROR, BEFORE_PROCESS, AFTER_PROCESS, ON_PROCESS_ERROR, BEFORE_READ, AFTER_READ, ON_READ_ERROR, ON_SKIP_IN_WRITE, ON_SKIP_IN_PROCESS, ON_SKIP_IN_READ};
    }

    public static ListenerMetaData[] stepExecutionListenerMetaData() {
        return new ListenerMetaData[]{BEFORE_STEP, AFTER_STEP};
    }

    public static ListenerMetaData[] taskletListenerMetaData() {
        return new ListenerMetaData[]{BEFORE_CHUNK, AFTER_CHUNK, AFTER_CHUNK_ERROR};
    }
}